#!/usr/bin/python
from Tkinter import *
from tkMessageBox import *
from tkFileDialog import *
from threading import Thread
import webbrowser
import os, sys

from Queue import Queue, Empty
from time import sleep

import urllib, shutil, tarfile

# TODO: is this really needed?
PYSVN_DIRS = [sys.path.append(os.path.dirname(__file__))]

DOWNLOAD_BASE = "http://easy-subversion.googlecode.com/files/"

# Status queue codes
EXIT = -1
HIDE_PROGRESS_BAR = -2

TEXT_LEFT = 382
TEXT_TOP = 277
TEXT_HEIGHT = 25
REFRESH_PERIOD_MS = 50
MAX_MESSAGES = 5
COLOR_MULT = 500
SNAP_SPEED = .3

BUTTONS_LEFT = 215
BUTTONS_TOP = 115
BUTTONS_SPACING = 50

ENTRY_OFFSET = 190
ENTRY_WIDTH = 150
ENTRY_HEIGHT = 22

BACK_LEFT = BUTTONS_LEFT
BACK_TOP = NEXT_TOP = 300
NEXT_LEFT = 500

class SetUpPySVN(Thread):
  def __init__(self, status_queue):
    super(SetUpPySVN, self).__init__()
    self.daemon = True
    self.status_queue = status_queue
    self.pysvn = None

  def import_pysvn(self):
    global pysvn
    pysvn = self.pysvn

  def install_pysvn(self):
    py_version = "%d.%d" % sys.version_info[:2]
    self.status_queue.put("Downloading PySVN for Python %s on %s..." % (py_version, sys.platform));
    # TODO: consider API version too!
    #api_version = sys.api_version
    file_name_base = "pysvn-%s-%s" % (sys.platform, py_version)
    file_name = file_name_base + ".tar.gz"
    download_url = DOWNLOAD_BASE + file_name
    target_file = os.path.join(os.path.dirname(__file__), file_name)

    print "Downloading..."

    def update_progress(block_count, block_size, total_size):
        self.status_queue.put(float(block_count * block_size) / total_size)

    try:
      print "Opening remote URL %s..." % download_url
      response = urllib.urlretrieve(download_url, target_file, update_progress)
      self.status_queue.put(HIDE_PROGRESS_BAR)
    except Exception as e:
      # TODO: better exception handling
      self.status_queue.put("Could not download the needed PySVN package\nPath: %s\nError: %s" % (download_url, e))
      os.remove(target_file)
      sys.exit(1)

    self.status_queue.put("Extracting...")
    tar_file = tarfile.open(target_file, mode='r:gz')
    tar_file.extractall(os.path.dirname(__file__))

  def load_pysvn(self):
    global pysvn
    # Check for PySVN.
    self.status_queue.put("Checking for PySVN...");

    while True:
      try:
        import pysvn
        self.status_queue.put("Found PySVN " + ".".join(map(str, pysvn.version)));
        self.pysvn = pysvn
        return
      except ImportError:
        self.status_queue.put("Trying another location...");
        # Try another directory...
        if len(PYSVN_DIRS) == 0:
          break
        sys.path.append(PYSVN_DIRS.pop())
      except:
        self.status_queue.put("Unknown error encountered.");
        raise
    self.install_pysvn()
    import pysvn

  def run(self):
    global svn_interface, wc_view

    # Import PySVN
    self.load_pysvn()

    # TODO: auto-update EasySVN

    # TODO: there must be a better way...
    import svn_interface
    import wc_view

    # Check for working copy in current folder.
    self.status_queue.put(EXIT)

class Meter(object):
  "Cheap replacement for Meter in Tix, to avoid requiring Tix."
  def __init__(self, master, text):
    self.label = Label(master, text=text)
    self.value = None

  def get_widget(self):
    return self.label

  def set_text(self, text):
    self.label.config(text=text)

  def set_progress(self, value):
    self.value = value

# Layer that reports the status of a long-running operation.
# TODO: allow asynchronous update of the status bar, instead
# of using a queue that might fill up...
class StatusLayer(object):
  def __init__(self, canvas, callback, messages):
    self.canvas = canvas
    self.callback = callback
    self.text_items = []
    self.messages = messages
    self.glide_distance = 0
    self.meter = Meter(canvas, text="0%")
    self.meter_item = canvas.create_window(TEXT_LEFT, TEXT_TOP,
      window = self.meter.get_widget(), anchor = NW, state = HIDDEN)

  def show(self):
    # Start checking for messages...
    self.canvas.after(REFRESH_PERIOD_MS, self.check_finish)

  # Update it regularly.
  def check_finish(self):
    try:
      while True:
        message = self.messages.get(False)

        if message == EXIT:
          while len(self.text_items) > 0:
              self.canvas.delete(self.text_items.pop())

          # Show the chooser options
          self.callback()

          return
        elif message == HIDE_PROGRESS_BAR:
          self.canvas.itemconfig(self.meter_item, state = HIDDEN)
        elif type(message) == float:
          self.canvas.itemconfig(self.meter_item, state = NORMAL)
          self.meter.set_text("%d%%" % int(100 * message))
          self.meter.set_progress(message)
        else:
          text_obj = self.canvas.create_text(TEXT_LEFT, TEXT_TOP +
              self.glide_distance,
              text=message, font=('Arial', 10, 'bold'), fill="black")
          self.text_items.append(text_obj)
          if len(self.text_items) > MAX_MESSAGES:
              self.canvas.delete(self.text_items[0])
              del self.text_items[0];
          self.glide_distance += TEXT_HEIGHT
    except Empty:
      pass

    if self.glide_distance > 0:
      dy = int(self.glide_distance * -SNAP_SPEED)
      self.glide_distance += dy
      for item in self.text_items:
        self.canvas.move(item, 0, dy)
        fill = (TEXT_TOP - int(self.canvas.coords(item)[1])) * COLOR_MULT / TEXT_TOP
        if fill < 0:
          fill = 0
        elif fill > 255:
          fill = 255

        self.canvas.itemconfig(item, fill="#%02x%02x%02x" % ((fill,) * 3))

    self.canvas.after(REFRESH_PERIOD_MS, self.check_finish)

  def close(self):
    self.canvas.delete(self.meter_item)
    pass

class CheckOutLayer(object):
  class Field:
    def __init__(self, title, canvas, info, left, top, password=False,
        callback=None):
      self.canvas = canvas
      self.title = title
      self.title_text = canvas.create_text(left, top, anchor = NW,
          font = ('Arial', 10, 'bold'), text = title)
      self.entry_widget = Entry(canvas)
      if password:
          self.entry_widget.config(show="*")
      self.entry_item = canvas.create_window(left + ENTRY_OFFSET, top,
          anchor = NW, window = self.entry_widget, width = ENTRY_WIDTH)
      self.info = canvas.create_text(left, top + ENTRY_HEIGHT,
          anchor = NW, font = ('Arial', 8), text = info)
      if callback:
          canvas.itemconfig(self.info, fill='blue')
          canvas.tag_bind(self.info, "<Button-1>", callback)

    def remove(self):
      self.canvas.delete(self.title_text)
      self.canvas.delete(self.info)
      self.canvas.delete(self.entry_item)

    def get(self):
      return self.entry_widget.get()

    def set(self, value):
      self.entry_widget.delete(0, END)
      self.entry_widget.insert(0, value)

    def focus(self):
      self.entry_widget.focus_set()

  def __init__(self, canvas, screen, arrows, cancel_callback,
      check_out_copy):
    self.check_out_copy = check_out_copy
    self.canvas = canvas
    self.screen = screen
    self.arrows = arrows
    self.cancel_callback = cancel_callback
    self.items = []
    self.project_title = self.Field('Your Project ID', canvas,
        'Example: ohs-gdc-roy', BUTTONS_LEFT,
        BUTTONS_TOP)
    self.username = self.Field('Your Gmail Address', canvas,
        'Example: solofencer@gmail.com', BUTTONS_LEFT,
        BUTTONS_TOP + ENTRY_HEIGHT * 2)
    self.password = self.Field('GoogleCode Password', canvas,
        'Click here to get it.', BUTTONS_LEFT,
        BUTTONS_TOP + ENTRY_HEIGHT * 4, password=True,
        callback = self.find_google_code_password)
    self.path = self.Field('Project Path', canvas,
        'Choose Path...', BUTTONS_LEFT,
        BUTTONS_TOP + ENTRY_HEIGHT * 6,
        callback = self.pick_path)
    self.path.set(os.path.dirname(__file__))
    self.fields = [self.project_title, self.username, self.password,
        self.path]

  def pick_path(self, ev):
    path = askdirectory(initialdir=self.path.get(), title="Pick Working Copy Folder")
    if path:
      path = path.replace('/', os.sep)
      self.path.set(path)

  def find_google_code_password(self, ev):
    webbrowser.open("https://code.google.com/hosting/settings",
        autoraise=True)

  def close(self):
    for field in self.fields:
      field.remove()
    self.arrows.hide()

  def attempt_checkout(self):
    if not self.project_title.get():
      showerror("Missing Info", "Please enter a project ID.")
      self.project_title.focus()
      return

    if not self.username.get():
      showerror("Missing Info", "Please enter a Gmail address.")
      self.username.focus()
      return

    if not self.password.get():
      showerror("Missing Info", "Please enter a Google Code password.")
      self.password.focus()
      return

    if not self.path.get():
      showerror("Missing Info", "Please choose a place to put the working copy.")
      self.path.focus()
      return

    repo = svn_interface.GoogleCodeRepository(self.project_title.get(),
      svn_interface.Credentials(self.username.get(), self.password.get()))

    path_to_use = os.path.join(self.path.get(), self.project_title.get())
    self.check_out_copy(repo, path_to_use)

  def cancel_checkout(self):
    self.cancel_callback()

  def show(self):
    self.arrows.show()
    self.arrows.next_btn.config(command=self.attempt_checkout)
    self.arrows.back_btn.config(command=self.cancel_checkout)

class BranchLayer(object):
  def __init__(self, canvas, screen):
    self.canvas = canvas
    self.screen = screen
    self.items = []
    # TODO

  def show(self):
    # TODO
    pass

class ChooserLayer(object):
  def __init__(self, canvas, screen):
    self.canvas = canvas
    self.screen = screen
    top = BUTTONS_TOP
    self.items = []

    # TODO: check to see if this actually exists.
    self.current_copy = Button(self.canvas,
        text = "Open current working copy",
        command=screen.open_current_copy_ev)
    self.items.append(self.canvas.create_window(BUTTONS_LEFT, top,
        window = self.current_copy, anchor=NW, state=HIDDEN))
    top += BUTTONS_SPACING

    self.checkout = Button(self.canvas,
        text = "Check out from repository...",
        command=screen.check_out)
    self.items.append(self.canvas.create_window(BUTTONS_LEFT, top,
        window = self.checkout, anchor=NW, state=HIDDEN))
    top += BUTTONS_SPACING

    self.open_existing = Button(self.canvas, text = "Open existing working copy...", command=screen.open_working_copy)
    self.items.append(self.canvas.create_window(BUTTONS_LEFT, top,
        window = self.open_existing, anchor=NW, state=HIDDEN))
    top += BUTTONS_SPACING

  def show(self):
    # Let user pick project.
    for item in self.items:
      self.canvas.itemconfig(item, state=NORMAL)

  # TODO: abstract this away in a better way?
  def close(self):
    for item in self.items:
      self.canvas.delete(item)

# Back and forward arrows
class ArrowLayer(object):
  def __init__(self, canvas):
    self.canvas = canvas

    self.back_btn = Button(canvas, text='Back')
    self.back_item = canvas.create_window(BACK_LEFT, BACK_TOP,
        anchor = NW, window = self.back_btn, state = HIDDEN)

    self.next_btn = Button(canvas, text='Next')
    self.next_item = canvas.create_window(NEXT_LEFT, NEXT_TOP,
        anchor = NW, window = self.next_btn, state = HIDDEN)

    self.items = [self.back_item, self.next_item]

  def show(self):
    for item in self.items:
      self.canvas.itemconfig(item, state=NORMAL)

  def hide(self):
    for item in self.items:
      self.canvas.itemconfig(item, state=HIDDEN)

# Throw up a loading screen.
class LoadingScreen(object):
  def __init__(self):
    self.messages = Queue()
    self.master = master = Tk()
    self.canvas = canvas = Canvas(master, width=587, height=353)
    self.path = None
    canvas.pack()
    canvas.config(highlightthickness=0)
    master.resizable(width=FALSE, height=FALSE)
    master.configure(width=587, height=353)
    master.title("Loading EasySVN...")

    self.img = img = PhotoImage(file=os.path.join(os.path.dirname(__file__), "title_screen.ppm"))
    canvas.create_image(0, 0, image = img, anchor = NW)
    # TODO: test this on Macs:
    #master.overrideredirect(True)

    self.layer = StatusLayer(self.canvas, self.show_chooser,
      self.messages)

    self.arrows = ArrowLayer(self.canvas)

  def run(self):
    self.layer.show()
    self.setup = SetUpPySVN(self.messages)
    self.setup.start()

    # Show the screen.
    self.master.mainloop()

  def show_chooser(self):
    self.setup.import_pysvn()

    self.layer.close()
    del self.layer
    self.layer = ChooserLayer(self.canvas, self)
    self.layer.show()

  def open_working_copy(self, event=None):
    # Pick a working copy to open
    self.path = askdirectory()
    if not self.path:
      return

    self.open_current_copy()

  def open_current_copy_ev(self, event=None):
    self.open_current_copy()

  def open_current_copy(self, wc=None):
    # Open the working copy in the current folder
    if wc:
      self.wc = wc
    else:
      try:
        if not self.path:
          self.path = os.path.dirname(__file__)
        self.wc = svn_interface.WorkingCopy(self.path)
      except pysvn.ClientError as e:
        print e
        showerror("SVN Error", "Error opening working copy in %s.  Maybe it's not a working copy?" % self.path)
        return

    v = wc_view.WorkingCopyView(self.wc)
    self.master.destroy()
    v.run()

  def do_checkout(self, repo, path):
    def on_complete():
      self.open_current_copy(wc)

    # TODO: really close?
    self.layer.close()
    self.path = path
    messages = Queue()
    self.layer = StatusLayer(self.canvas, on_complete,
        messages)
    self.layer.show()

    # TODO: get back button working
    #args = (path, messages)

    def thread_wrapper():
      global wc
      wc = repo.check_out_wc(path, messages)
      messages.put(EXIT)

    thread = Thread(target=thread_wrapper) #, args=args)
    thread.daemon = True
    thread.start()

  def check_out(self, ev=None):
    # Ask for project details
    self.layer.close()
    self.layer = CheckOutLayer(self.canvas, self, self.arrows,
      self.show_chooser, self.do_checkout)
    self.layer.show()
